package com.zdp.service;

import com.zdp.common.JsonUtils;
import com.zdp.domain.SkillEntity;
import com.zdp.domain.SkillGoods;
import com.zdp.domain.SkillOrder;
import com.zdp.mapper.SkillOrderMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

/**
 * @author sesshomaru
 * @date 2021/7/24 22:58
 */
@Service
public class SkillGoodsService {

    public static final String SKILL_GOODS_PHONE = "SKILL_GOODS_PHONE";

    public static final String SKILL_GOODS_LIST = "SKILL_GOODS_LIST";
    // 防止用户重复下单
    public static final String SKILL_GOODS_ONLY = "SKILL_GOODS_ONLY";
    // 库存队列
    public static final String SKILL_GOODS_QUEUE = "SKILL_GOODS_QUEUE";
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private MutilThreadOrder mutilThreadOrder;
    @Autowired
    private ProductService productService;

    @Transactional
    public void add(Long productId, String userId) throws Exception {
        // 判断这个用户是否参加过抢单(redis的incr是原子计数器并发完全),解决重复抢单
        // 因为redis有一个特性，它对于同一个key的命令的操作都是单线程的，就是一个命令接着一个命令去执行, 一个线程等待上一个线程执行完才会被redis处理
        // 即使是同一个用户的两个并发请求过来，也是一个接着一个执行,并发安全
        // 可以使用在，重复支付，重复消息，重复下单等类式的功能
        // 展示注释掉用来测试超卖
//        Long time = redisTemplate.boundHashOps(SKILL_GOODS_ONLY).increment(userId, 1);
//        if (time > 1) {
//            throw new Exception("重复抢单, 别太贪心呦");
//        }

        // 解决超卖，步骤1 取出redis中的库存队列，如果取不出就表示秒杀商品已经没了
        // 这个只能保证缓存中的库存只准确的，不能保证下单时不会重复
        // 采用redis的list来解决超卖问题, 取的出数据就表示有库存可以继续往下走
        Long stockId = (Long) redisTemplate.boundListOps(SKILL_GOODS_QUEUE + productId).rightPop();
        if (stockId == null) {
            System.out.println("该商品已被秒杀完毕");
            // 删除防止用户重复下单的key
            redisTemplate.boundHashOps(SKILL_GOODS_ONLY).rename(userId);
            SkillGoods skillGoods = productService.queryByProductId(productId);
            // 将商品从redis中删除，并更新数据库状态
            redisTemplate.boundHashOps(SKILL_GOODS_PHONE).delete(String.valueOf(skillGoods.getId()));
            productService.update(skillGoods);
            return;
        }
        // 继续往下就是从商品队列中，获取到商品库存了，就往下走,但是往下仍然有超卖的风险，因为还是可能存在多个符合条件的线程在创建订单
        // 时读取到相同库存数的情况

        // 封装对象并放入redis队列
        SkillEntity skillEntity = new SkillEntity();
        skillEntity.setProductId(productId);
        skillEntity.setUserId(userId);
        // 使用redis队列，左侧将数据插入在右侧将数据取出
        redisTemplate.boundListOps(SKILL_GOODS_LIST).leftPush(skillEntity);
        // 异步完成下单操作
        mutilThreadOrder.createOrder();
        System.out.println("已将抢单信息放入redis队列，等待异步创建订单线程处理");
    }
}
